# -*- coding: utf-8 -*-
from nmj.core.constants import REGEXP_SHOW_WITH_EPISODE, REGEXP_SHOW_WITH_SEASON, \
	REGEXP_MOVIES, REGEXP_SHOW_WITH_SEASON_EPISODE
from nmj.scanners.local import DirectoryScanner
from nmj.scanners.regexp_based_scanner import RegexpBasedScanner, TVShowCleaner, \
	MovieCleaner
from nmj.scanners.tmdb_3 import TMDb3Scanner
from nmj.scanners.ttvdb import TheTVDbScanner
from nmj.sqlite.nmj_db import NMJDB
from nmj.sqlite.tables import ALL_TABLES, Video, Show, VideoSynopsis, VideoAttr, \
	VideoPerson, VideoGroup, VideoGroupName
from nmj.tools.helpers import tupleize
import glob
import logging
import os
import time


__version__ = "0.1"
_LOGGER = logging.getLogger(__name__)
MEDIA_MOVIE = "movie"

class AbstractProgressBar(object):
	def __init__(self, *args, **kwargs):
		pass
	def start(self):
		pass
	def update(self, rank):
		pass
	def finish(self):
		pass
class UpdateReport(object):
	def __init__(self):
		self._data = {
			"known" : [],
			"no data found" : [],
			"updated" : [],
		}
	def __str__(self):
		return """Update report:
		%d already known
		%d updated
		%d not found
		""" % (len(self._data["known"]), len(self._data["updated"]), len(self._data["no data found"]))

	def add_already_known(self, media):
		self._data["known"].append(media)
	def add_updated(self, media):
		self._data["updated"].append(media)
	def add_not_found(self, media):
		self._data["no data found"].append(media)
	def get_media_not_found(self):
		return self._data["no data found"]
	def get_already_known_media(self):
		return self._data["known"]
	def get_new_media(self):
		return self._data["updated"]

class retry(object):
	def __init__(self, max_retries, time_before_two_retries):
		self._max_retries = max_retries
		self._time_before_two_retries = time_before_two_retries

	def __call__(self, functor):
		def retriable(*args, **kwargs):
			retry = self._max_retries
			while retry:
				try:
					return functor(*args, **kwargs)
				except:
					_LOGGER.exception("Error while executing %s", functor)
					retry -= 1
					if not retry:
						raise
					else:
						time.sleep(self._time_before_two_retries)
		return retriable

class Library(object):
	def __init__(self, local_directory, web_scanner=None):
		self.nmj_db = NMJDB(local_directory)
		self.scanner = web_scanner or RegexpBasedScanner(
			(REGEXP_SHOW_WITH_EPISODE, TheTVDbScanner(), TVShowCleaner(), "TV Show with episode"),
			(REGEXP_SHOW_WITH_SEASON_EPISODE, TheTVDbScanner(), TVShowCleaner(), "TV Show with season and episode"),
			(REGEXP_SHOW_WITH_SEASON, TheTVDbScanner(), TVShowCleaner(), "TV Show with season"),
			(REGEXP_MOVIES, TMDb3Scanner(), MovieCleaner(), "Movie"),
		)
		self.base_directory = local_directory
		self.callback = lambda x : x

	def __str__(self):
		return "Media library on %s" % self.base_directory

	def __repr__(self):
		return "Media library on %s" % self.base_directory

	def set_callback(self, callback):
		self.callback = callback

	def purge(self):
		self.nmj_db.purge()
		for filename in glob.glob(os.path.join(self.nmj_db.image_directory, "*")):
			try:
				os.remove(filename)
			except OSError:
				pass

	def clean_movie_names(self):
		movie_files = DirectoryScanner(self.base_directory).movie_files
		for source in movie_files:
			_LOGGER.debug("rename %s %s", source, type(source))
			os.rename(source.path, os.path.join(source.location, "%s%s" % (self.scanner.clean_title(source.title), source.extension)))

	def clean(self):
		self.callback("Start cleaning")
		shows_to_update = set()
		known_videos = self.nmj_db.get_tables_items(Video)
		for video in known_videos:
			try:
				real_path = os.path.join(self.base_directory, video.path)
				if not os.path.exists(real_path):
					_LOGGER.info("Deleting video %s (%s) as it does not exist on disk anymore", video.title, video.path)
					self.callback("Delete video %s (%s)" % (video.title, video.path))
					shows_to_update.add(video.show_id)
					self.nmj_db.delete(video)
					self.nmj_db.search_and_delete(VideoSynopsis, video_id=video.video_id)
					self.nmj_db.search_and_delete(VideoAttr, video_id=video.video_id)
					groups = self.nmj_db.get_tables_items(VideoGroup)
					for group in groups:
						self.nmj_db.delete(group)
						self.nmj_db.search_and_delete(VideoGroupName, video_group_name_id=group.video_group_id)
			except:
				_LOGGER.exception("oups, something weird happened with %s", video.path)

		for show_id in shows_to_update:
			self.nmj_db.update_show_total_item(show_id)

		empty_shows = self.nmj_db.get_tables_items(Show, total_item=0)
		for show in empty_shows:
			self.callback("Delete empty show %s" % (show.title))
			self.nmj_db.delete(show)

	def __insert_movie(self, movie):
		_LOGGER.info("Inserting in DB %s", movie)
		movie.get_poster(self.nmj_db.image_directory)
		movie.get_fanart(self.nmj_db.image_directory)
		self.nmj_db.insert_movie(movie)
		_LOGGER.debug("Movie insertion done")

	@retry(3, 30)
	def search(self, movie_title):
		return self.scanner.search(movie_title)

	@retry(3, 30)
	def retreive_movie_informations(self, search_result):
		return search_result.get_media()

	def update(self, progress_bar=None):
		self.callback("Start updating")
		report = UpdateReport()
		movie_files = DirectoryScanner(self.base_directory).movie_files
		_LOGGER.info("Found %d media files in %s", len(movie_files), self.base_directory)
		self.callback("Found %s media files in %s" % (len(movie_files), self.base_directory))
		known_videos_paths = [video.path for video in self.nmj_db.get_tables_items(Video)]
#		widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), ' ',progressbar.ETA()]
		if progress_bar:
			bar = progress_bar(len(movie_files))#, ["Update" + ": "] + widgets)
			bar.start()
		for rank, movie in enumerate(movie_files):
			_LOGGER.info("Updating (%d/%d) : %s", rank, len(movie_files), movie.title)
			if movie.path.replace(self.base_directory + "/", "") not in known_videos_paths:
				_LOGGER.debug("Movie %s (%s) not yet in DB", movie.path, movie.path.replace(self.base_directory + "/", ""))
				self.callback("Inserting video %s (%s)" % (movie.title, movie.path))
				results = self.search(movie.title)
				if results:
					movie_info = self.retreive_movie_informations(results[0])
					movie_info.update_attr(
						filename=movie.filename,
						file_title=movie.title,
						path=movie.path.replace(self.base_directory + "/", ""),
						size=os.path.getsize(movie.path),
					)
					self.__insert_movie(movie_info)
					self.callback("Video %s (%s) succesfully inserted" % (movie.title, movie.path))
					report.add_updated(movie)
				else:
					report.add_not_found(movie)
					self.callback("No result found for %s (%s)" % (movie.title, movie.path))
					_LOGGER.warning("no result found for %s (%s)", movie, movie.title)
			else:
				report.add_already_known(movie)
			if progress_bar:
				bar.update(rank)
		if progress_bar:
			bar.finish()
		self.callback("Update done")
		return report

	def get_all(self, *tables, **kwargs):
		return self.nmj_db.get_tables_items(*tables, **kwargs)

	def get_show_from_title(self, title):
		if not title:
			return None
		try:
			return self.nmj_db.get_tables_items(Show, title=title)[0]
		except IndexError:
			return None

	def get_synopsis(self, title=None, show=None):
		try:
			show = show or self.get_show_from_title(title)
			if show:
				return self.nmj_db.get_tables_items(VideoSynopsis, show_id=show.show_id)[0].content
			return ""
		except IndexError:
			return ""

	def get_posters(self, title=None, show=None):
		show = show or self.get_show_from_title(title)
		if show:
			result = self.search(show.title)
			return self.retreive_movie_informations(result[0]).posters
		return []

	def get_wallpapers(self, title=None, show=None):
		show = show or self.get_show_from_title(title)
		if show:
			result = self.search(show.title)
			return self.retreive_movie_informations(result[0]).fanarts
		return []

	def update_poster(self, show, poster):
		image_path = poster.get_image(self.nmj_db.image_directory)
		self.update_item(show, detail_poster=self.nmj_db.to_nmj_path(image_path))
		return image_path

	def update_wallpaper(self, show, wallpaper):
		image_path = wallpaper.get_image(self.nmj_db.image_directory)
		self.update_item(show, wallpaper=self.nmj_db.to_nmj_path(image_path))
		return image_path

	def update_show(self, show, movie_info):
		full_info = self.retreive_movie_informations(movie_info)
		full_info.update_attr(
			filename=movie_info.path,
			file_title=movie_info.title,
			path=movie_info.path.replace(self.base_directory + "/", ""),
			size=os.path.getsize(movie_info.path),
		)
		self.__insert_movie(full_info)
		self.nmj_db.delete(show)
		return self.nmj_db.get_tables_items(Show, title=movie_info.title)[0]

	def update_item(self, item, **kwargs):
		return self.nmj_db.update(item, **kwargs)

	def get_shows(self, full=False, **kwargs):
		shows = self.get_all(Show, **kwargs)
		for show in shows:
			show.library=self
			if show.detail_poster:
				show.detail_poster = os.path.join(self.base_directory, show.detail_poster)
			if show.wallpaper:
				show.wallpaper = os.path.join(self.base_directory, show.wallpaper)
			if full:
				show.videos = self.get_all(Video, show_id=show.show_id)
				for video in show.videos:
					video.synopsis = self.get_all(VideoSynopsis, video_id=video.video_id)
					for attr in self.get_all(VideoAttr, video_id=video.video_id):
						value = getattr(video, attr.type, [])
						setattr(video, attr.type, value + [attr.value,])
					video.actors = []
					for cast in tupleize(eval(video.cast_id)):
						try:
							video.actors.extend(self.get_all(VideoPerson, video_person_id=cast)[0].name)
						except IndexError:
							pass
		return shows

class NMJUpdater(object):
	def __init__(self):
		self.libraries = []

	def add_library(self, base_directory, scanner=None):
		self.libraries.append(Library(base_directory, scanner))

	def update(self, progress_bar=None):
		reports = []
		for lib in self.libraries:
			report = lib.update(progress_bar or AbstractProgressBar)
			reports.append(report)
			_LOGGER.info("Update %s. %s", lib, report)
		return reports

	def search(self, title):
		for lib in self.libraries:
			result = lib.search(title)
			if result:
				return result
		return []

	def purge(self):
		for lib in self.libraries:
			lib.purge()

	def clean_movie_names(self):
		for lib in self.libraries:
			lib.clean_movie_names()

	def clean(self):
		for lib in self.libraries:
			lib.clean()

	def get_all(self, *tables):
		result = []
		for lib in self.libraries:
			result += lib.get_all(*tables)
		return result

	def get_medias(self):
		result = []
		for lib in self.libraries:
			result += lib.movies
		return result

	def get_synopsis(self, show=None, title=None):
		for lib in self.libraries:
			synopsis = lib.get_synopsis(show=show, title=title)
			if synopsis:
				return synopsis
		return ""

	def get_posters(self, show=None, title=None):
		for lib in self.libraries:
			result = lib.get_posters(show=show, title=title)
			if result:
				return result
		return []

def parse_parameters():
	parser = optparse.OptionParser(version=__version__)

	parser.add_option(
		"-s", "--scan",
		dest="scan", action="store_true", default=False,
		help="Scan directory and retrieve info on the web for new media")

	parser.add_option(
		"-v", "--show-video",
		dest="show_video", action="store_true", default=False,
		help="Show Video database content")

	parser.add_option(
		"-a", "--show-all",
		dest="show_all", action="store_true", default=False,
		help="Show all database content")

	parser.add_option(
		"-m", "--search-movie",
		dest="search_movie", default="", type="string",
		help="Show all database content")

	parser.add_option(
		"-p", "--purge",
		dest="purge", action="store_true", default=False,
		help="Purge database",
	)
	parser.add_option(
		"-c", "--clean",
		dest="clean", action="store_true", default=False,
		help="Clean videos that are not on disk anymore",
	)
	parser.add_option(
		"-n", "--clean-name",
		dest="clean_name", action="store_true", default=False,
		help="Clean videos file names",
	)
	parser.add_option(
		"-g", "--get-posters",
		dest="get_posters", type="string", default="",
		help="Get videos posters",
	)

	parser.add_option(
		"-l", "--loglevel",
		dest="log_level", type="choice",
		choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
		help="PyNMJ log level", default="INFO",
	)
	return parser.parse_args()


if __name__ == "__main__":
	import optparse
	try:
		from progressbar import ProgressBar
	except ImportError:
		print "You should install python-progressbar."
		ProgressBar = AbstractProgressBar
	options, arguments = parse_parameters()
	nmj = NMJUpdater()
	logging.basicConfig(format='%(asctime)-15s %(message)s', filename="pynmj.log", level=getattr(logging, options.log_level))

	if arguments:
		nmj.add_library(os.path.normpath(arguments[0]))
	if options.purge:
		nmj.purge()
	if options.clean:
		nmj.clean()
	if options.clean_name:
		nmj.clean_movie_names()
	if options.search_movie:
		for result in nmj.search(options.search_movie):
			print "%s" % result
		else:
			print "No result found"
	if options.get_posters:
		nmj.get_posters(title=options.get_posters)
	if options.scan:
		for report in nmj.update(ProgressBar):
			print "%s" % report
			print "Media not found are:"
			for media in report.get_media_not_found():
				print "%s" % media
	if options.show_video:
		for item in nmj.libraries[0].get_all(Video):
			print "%s" % item
	if options.show_all:
		for item in nmj.libraries[0].get_all(*ALL_TABLES):
			_LOGGER.info("%s", item)
